package com.izkml.mlyun.hotreload.service;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpStatus;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import cn.hutool.system.SystemUtil;
import com.izkml.mlyun.hotreload.config.AppProperties;
import com.izkml.mlyun.hotreload.constant.Const;
import com.izkml.mlyun.hotreload.enums.OtherCmdEnums;
import com.izkml.mlyun.hotreload.model.CallBack;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.HashMap;

@Slf4j
@Service
public class CallBackService {

    @Resource
    private AppProperties appProperties;

    @Resource
    private ManageService manageService;

    private final String appServerPort;

    private final String localhostIP;

    public CallBackService() {
        String appServerPort = System.getenv(Const.ENV.APP_SERVER_PORT);
        if (StrUtil.isBlank(appServerPort)) {
            appServerPort = Const.ENV.APP_DEFAULT_SERVER_PORT;
        }
        this.appServerPort = appServerPort;
        // todo: 暂不考虑多网卡
        this.localhostIP = SystemUtil.getHostInfo().getAddress();
    }

    /**
     * 回调接口
     */
    protected void callBack(CallBack callBack) {
        class CallBackThread implements Runnable {

            private final CallBack callBack;

            public CallBackThread(CallBack manageCallBack) {
                this.callBack = manageCallBack;
            }

            @Override
            public void run() {
                String callbackUrl = callBack.getUrl();
                String callBackBody = callBack.getBody();

                String prettyBody = JSONUtil.toJsonPrettyStr(JSONUtil.toBean(callBackBody, HashMap.class));

                long start = DateUtil.currentSeconds();
                log.info("回调 >>> 地址：{}，开始 -> {}，数据体：\n{}", callbackUrl, start, prettyBody);

                try {
                    callBackBus(callBack);
                } catch (RuntimeException e) {
                    long end = DateUtil.currentSeconds();
                    log.error("回调 >>> 地址：{}，结果：失败，结束 -> {}，耗时：{} 秒，数据体：\n{}", callbackUrl, end, end - start, prettyBody);
                    return;
                }

                long end = DateUtil.currentSeconds();
                log.info("回调 >>> 地址：{}，结果：成功，结束 -> {}，耗时：{} 秒，数据体：\n{}", callbackUrl, end, end - start, prettyBody);
            }
        }

        if (ObjectUtil.isNull(callBack)) {
            log.warn("回调对象为 null，跳过回调");
            return;
        }

        String callbackUrl = callBack.getUrl();
        String callBackBody = callBack.getBody();

        if (StrUtil.hasEmpty(callbackUrl, callBackBody)) {
            log.warn("完整地址 {} 或业务传参 body {} 有一项为空，跳过回调", callbackUrl, callBackBody);
            return;
        }
        ThreadUtil.execAsync(new CallBackThread(callBack));
    }

    /**
     * 回调总线
     */
    private void callBackBus(CallBack callBack) throws RuntimeException {
        log.info("回调参数：\n{}", JSONUtil.toJsonPrettyStr(callBack));

        String manualCheckTips = StrUtil.format("{}，请人工自检", OtherCmdEnums.tail_100_app_log.getLabel());

        boolean health = healthCheck(callBack.getHealthCheckUri(), callBack.getHealthCheckStatusCode());
        if (!health) {
            log.error("业务 JVM 健康检查失败，{}", manualCheckTips);
            manageService.tail100AppLog();
            throw new RuntimeException();
        }

        boolean openApi = openApiCheck();
        if (!openApi) {
            log.error("OpenApiSaverTest 进程 hang 住，{}", manualCheckTips);
            manageService.tail100AppLog();
            throw new RuntimeException();
        }

        callBackHandler(callBack);
    }

    /**
     * 回调处理
     */
    private void callBackHandler(CallBack callBack) throws RuntimeException {
        String callbackUrl = callBack.getUrl();

        // 根据 body 传参，追加 apiDocs 到 body
        callBack.appendApiDocs(getApiDocs(callBack.getAppendApiDocs()));

        // 构造 request
        HttpRequest request = HttpUtil.createRequest(callBack.getMethod(), callbackUrl)
                .body(callBack.getBody())
                .header(callBack.getHeader(), callBack.getToken())
                .timeout(appProperties.getCallBack().getTimeoutSeconds() * 1000);

        // 发起请求，打印日志
        try (HttpResponse response = request.execute()) {
            int status = response.getStatus();
            String responseBody = response.body();
            if (status != HttpStatus.HTTP_OK) {
                log.warn("回调地址：{}，状态码：{}，响应体：{}", callbackUrl, status, responseBody);
            } else {
                log.debug("回调地址：{}，状态码：{}，响应体：{}", callbackUrl, status, responseBody);
            }
        } catch (Exception e) {
            log.error("回调地址：{}，请求错误：{}", callbackUrl, e.getMessage());
            throw new RuntimeException();
        }
    }


    /**
     * 获取 apiDocs 的响应数据
     *
     * @param appendApiDocs 回调 body 中追加 apiDocs 的特性开关的值
     * @return 反序列化的 JSONObject，如：{"swagger": "2.0"}
     */
    private JSONObject getApiDocs(boolean appendApiDocs) throws RuntimeException {
        if (!appendApiDocs) {
            return null;
        }

        String apiDocsUrl = StrUtil.format("{}:{}{}", localhostIP, appServerPort, Const.APP.API_DOCS_URI);
        try (HttpResponse response = HttpUtil.createGet(apiDocsUrl).timeout(3000).execute()) {
            return JSONUtil.parseObj(response.body());
        } catch (Exception e) {
            log.error("获取 apiDocs 失败，URL：{}，异常信息：{}", apiDocsUrl, e.getMessage());
            throw new RuntimeException();
        }
    }

    /**
     * 健康检查
     *
     * @param uri        /actuator/health
     * @param statusCode 200|302
     * @return true/false
     */
    private boolean healthCheck(String uri, String statusCode) {
        try {
            Thread.sleep(appProperties.getHealthCheck().getInitialDelaySeconds() * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        String checkUrl = StrUtil.format("{}:{}{}", Const.APP.HTTP_LOCALHOST, appServerPort, uri);
        HttpRequest request = HttpUtil.createGet(checkUrl)
                .timeout(appProperties.getHealthCheck().getTimeoutSeconds() * 1000);

        int time = 0;
        while (time < appProperties.getHealthCheck().getFailureThreshold()) {
            time += 1;
            int status;
            try {
                Thread.sleep(appProperties.getHealthCheck().getPeriodSeconds() * 1000);
                status = request.execute().getStatus();
                log.info("业务 JVM 第 {} 次健康检查，URL: {}，状态码：{}", time, checkUrl, status);
            } catch (Exception e) {
                log.error("业务 JVM 第 {} 次健康检查，URL: {}，异常信息：{}", time, checkUrl, e.getMessage());
                continue;
            }
            if (StrUtil.contains(statusCode, String.valueOf(status))) {
                return true;
            }
        }
        return false;
    }

    /**
     * openApi 检查
     *
     * @return true不在/false正在
     */
    private boolean openApiCheck() {
        int time = 0;
        while (time < appProperties.getOpenApiCheck().getMaxTimeThreshold()) {
            time += 1;
            boolean testing;
            try {
                Thread.sleep(appProperties.getOpenApiCheck().getPeriodSeconds() * 1000);
                testing = manageService.testing();
                log.info("OpenApiSaverTest 进程第 {} 次存在性检查，结果：{}", time, testing);
            } catch (Exception e) {
                log.error("OpenApiSaverTest 进程第 {} 次存在性检查，异常信息：{}", time, e.getMessage());
                continue;
            }
            if (!testing) {
                return true;
            }
        }
        return false;
    }
}
